package com.moon.back.util;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.PrimitiveArrayUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.moon.back.config.CustomCacheProperties;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.SerializationUtils;

import javax.annotation.Nonnull;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.function.Supplier;

/**
 * RefreshCacheServiceImpl
 *
 * implements IRefreshCacheService
 *
 * @author <a href="drakelee1221@gmail.com">LiGeng</a>
 * @version 1.0.0, 2025-04-09 14:02
 */
@Service
public class RefreshCacheServiceImpl implements IRefreshCacheService {

    @Resource
    private CustomCacheProperties customCacheProperties;
    @Resource
    private MoonRedisStringUtil sanziRedisStringUtil;
    @Override
    public <T> T queryOrRefresh(@Nonnull Supplier<T> dataQuery){
        return queryOrRefresh(dataQuery.getClass().getName(), null, dataQuery);
    }
    @Override
    public <T> T queryOrRefresh(Serializable queryCondition, @Nonnull Supplier<T> dataQuery){
        return queryOrRefresh(dataQuery.getClass().getName(), queryCondition, dataQuery);
    }
    @Override
    public <T> T queryOrRefresh(String queryName, @Nonnull Supplier<T> dataQuery){
        return queryOrRefresh(queryName, null, dataQuery);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T queryOrRefresh(String queryName, Serializable queryCondition, @Nonnull Supplier<T> dataQuery){
        Assert.notNull(dataQuery, "dataQuery is not null");
        if (CharSequenceUtil.isBlank(queryName)) {
            queryName = dataQuery.getClass().getName();
        }
        String key = buildKey(queryName, queryCondition);
        Object o = sanziRedisStringUtil.get(key);
        long expireSeconds = getExpireSeconds(queryName);
        if(o == null){
            o = dataQuery.get();
            sanziRedisStringUtil.setex(key, o, expireSeconds);
        }
        else{
            refreshCache(queryName, key, expireSeconds, dataQuery);
        }

        return (T) o;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> void insertData(String queryName, @Nonnull Supplier<T> dataInsert){
        Assert.notNull(dataInsert, "dataQuery is not null");
        if (CharSequenceUtil.isBlank(queryName)) {
            queryName = dataInsert.getClass().getName();
        }
        String key = buildKey(queryName, "insert");

//        Object o = sanziRedisStringUtil.get(key);
//        long expireSeconds = getExpireSeconds(queryName);

        insertToMysql(queryName, key, dataInsert);



    }

  @Override
  public <T> void insertToMysql(String insertName, String key ,  Supplier<T> insertQuery){
        Long ttlSeconds = sanziRedisStringUtil.ttl(key);
        if(ttlSeconds == null || ttlSeconds < 0){
            ttlSeconds = 0L;
        }
        long refreshSeconds = getRefreshSeconds(insertName);
        // 如果缓存存活时间超过需要刷新的时间，则刷新缓存数据
           // 插入操作时候加上分布式锁
            String lockKey = buildLockKey(key);
            if (BooleanUtil.isTrue(sanziRedisStringUtil.setnxAndExpire(lockKey, 1, refreshSeconds))) {
                AsyncUtils.execute(() -> {
                    try {
                        Object o = insertQuery.get();
                    } finally {
                        sanziRedisStringUtil.del(lockKey);
                    }
                });
        }
    }




    private <T> void refreshCache(String queryName, String key, long expireSeconds, Supplier<T> dataQuery){
        Long ttlSeconds = sanziRedisStringUtil.ttl(key);
        if(ttlSeconds == null || ttlSeconds < 0){
            ttlSeconds = 0L;
        }
        long refreshSeconds = getRefreshSeconds(queryName);
        // 如果缓存存活时间超过需要刷新的时间，则刷新缓存数据
        if (expireSeconds - ttlSeconds > refreshSeconds) {
            String lockKey = buildLockKey(key);
            if (BooleanUtil.isTrue(sanziRedisStringUtil.setnxAndExpire(lockKey, 1, refreshSeconds))) {
                AsyncUtils.execute(() -> {
                    try {
                        Object o = dataQuery.get();
                        if(o != null){
                            sanziRedisStringUtil.setex(key, o, expireSeconds);
                        }
                    } finally {
                        sanziRedisStringUtil.del(lockKey);
                    }
                });
            }
        }
    }

    private long getExpireSeconds(String queryName){
        long expire = customCacheProperties.getDefaultExpireMinutes();
        if(CollUtil.isNotEmpty(customCacheProperties.getSpecialExpireMinutes())){
            Long e = customCacheProperties.getSpecialExpireMinutes().get(queryName);
            if(e != null && e > 0){
                expire = e;
            }
        }
        return expire * 60;
    }

    private long getRefreshSeconds(String executeName){
        long expire = customCacheProperties.getDefaultRefreshMinutes();
        if(CollUtil.isNotEmpty(customCacheProperties.getSpecialRefreshMinutes())){
            Long e = customCacheProperties.getSpecialRefreshMinutes().get(executeName);
            if(e != null && e > 0){
                expire = e;
            }
        }
        return expire * 60;
    }

    private String buildLockKey(String key){
        return "DataCache:RefreshLock:" + key;
    }


    private static String sign(Object obj){
        byte[] serialize = SerializationUtils.serialize(obj);
        if(PrimitiveArrayUtil.isEmpty(serialize)){
            return "";
        }
        return DigestUtil.sha256Hex(serialize);
    }

    private static String buildKey(String queryName, Serializable queryCondition){
        String key = "DataCache:" + sign(queryName);
        String conditionKey = sign(queryCondition);
        if(CharSequenceUtil.isNotBlank(conditionKey)){
            key += ":" + conditionKey;
        }
        return key;
    }

}